﻿#region Copyright

// The contents of this file are subject to the Mozilla Public License
//  Version 1.1 (the "License"); you may not use this file except in compliance
//  with the License. You may obtain a copy of the License at
//  
//  http://www.mozilla.org/MPL/
//  
//  Software distributed under the License is distributed on an "AS IS"
//  basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
//  License for the specific language governing rights and limitations under 
//  the License.
//  
//  The Initial Developer of the Original Code is Robert Smyth.
//  Portions created by Robert Smyth are Copyright (C) 2008.
//  
//  All Rights Reserved.

#endregion

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading;
using NUnitGrid.Framework.Logging;
using NUnitGrid.Framework.Pipes.Client;


namespace NUnitGrid.Framework.Pipes
{
    public class PipeStream : IPipeStream, IScheduledEventListener
    {
        private readonly ILogger logger;
        private readonly List<string> messagesToSend = new List<string>();
        private readonly System.IO.Pipes.PipeStream pipeStream;
        private readonly IPipeReadListener readListener;
        private readonly TimeSpan readTickPeriod = TimeSpan.FromMilliseconds(200);
        private readonly List<string> receivedLines = new List<string>();
        private readonly ISchedule schedule;
        private Thread readThread;
        private StreamReader streamReader;
        private StreamWriter streamWriter;
        private Thread writeThread;

        public PipeStream(System.IO.Pipes.PipeStream pipeStream, ILogger logger, IPipeReadListener readListener,
                          ISchedule schedule)
        {
            this.pipeStream = pipeStream;
            this.logger = logger;
            this.readListener = readListener;
            this.schedule = schedule;
        }

        public void Start()
        {
            logger.Verbose.Write("Starting pipe stream");

            streamWriter = new StreamWriter(pipeStream) {AutoFlush = true};
            streamReader = new StreamReader(pipeStream);

            readThread = new Thread(ReadThreadProc) {Priority = ThreadPriority.Lowest};
            readThread.Start();

            writeThread = new Thread(WriteThreadProc);
            writeThread.Start();

            schedule.AddEvent(this, readTickPeriod);
        }

        public void WaitForConnection(PipeClientStream.OnConnect onConnect)
        {
            throw new InvalidOperationException();
        }

        public void WriteLine(string message)
        {
            logger.Verbose.Write("Sending messsage of {0} chars.", message.Length);
            messagesToSend.Add(message);
        }

        public void Stop()
        {
            if (streamWriter != null)
            {
                logger.Info.Write("Closing pipe.");

                try
                {
                    streamWriter.Close();
                }
                catch (Exception)
                {
                }

                try
                {
                    streamReader.Close();
                }
                catch (Exception)
                {
                }

                try
                {
                    readThread.Abort();
                }
                catch (Exception)
                {
                }

                try
                {
                    writeThread.Abort();
                }
                catch (Exception)
                {
                }
            }
        }

        void IScheduledEventListener.OnEventFired()
        {
            List<string> copyOfReceivedLines;
            lock (receivedLines)
            {
                copyOfReceivedLines = new List<string>(receivedLines);
                receivedLines.Clear();
            }

            if (copyOfReceivedLines.Count > 0)
            {
                var receivedMessage = new StringBuilder();
                foreach (var receivedLine in copyOfReceivedLines)
                {
                    receivedMessage.AppendLine(receivedLine);
                }

                readListener.OnReceivedLine(receivedMessage.ToString());
            }

            if (readThread.IsAlive && writeThread.IsAlive)
            {
                schedule.AddEvent(this, readTickPeriod);
            }
            else
            {
                logger.Info.Write("Pipe closed.");
            }
        }

        private void ReadThreadProc()
        {
            try
            {
                while (streamReader != null && pipeStream.IsConnected)
                {
                    var receivedLine = streamReader.ReadLine();

                    if (receivedLine != null)
                    {
                        lock (receivedLines)
                        {
                            receivedLines.Add(receivedLine);
                        }
                    }

                    if (streamReader.Peek() == -1)
                    {
                        Thread.Sleep(25);
                    }
                }
            }
            catch (IOException exception)
            {
                logger.Warning.Write("Read thread IOException '{0}'", exception);
            }
            catch (ThreadAbortException)
            {
            }
            catch (Exception exception)
            {
                logger.Error.Write("Read thread exception '{0}'", exception);
            }

            logger.Info.Write("Exiting pipe read thread.");
        }

        private void WriteThreadProc()
        {
            if (streamWriter != null)
            {
                try
                {
                    while (streamWriter != null && pipeStream.IsConnected)
                    {
                        var message = string.Empty;

                        lock (messagesToSend)
                        {
                            if (messagesToSend.Count > 0)
                            {
                                message = messagesToSend[0];
                                messagesToSend.RemoveAt(0);
                            }
                        }

                        if (message != string.Empty)
                        {
                            streamWriter.WriteLine(message);
                        }
                        Thread.Sleep(25);
                    }
                }
                catch (IOException expection)
                {
                    logger.Warning.Write("Write thread IOException: {0}.", expection.Message);
                }
                catch (ThreadAbortException)
                {
                }
                catch (Exception exception)
                {
                    logger.Error.Write("Write thread exception '{0}'", exception);
                }
            }

            logger.Info.Write("Exiting pipe write thread.");
        }
    }
}